#pragma once

#include <string>
#include <string_view>
#include <algorithm>
#include <cuchar>
#include <climits>
#include <locale>
#include <cwchar>
#include <cstdio>
#include <filesystem>

namespace zenox {

inline std::string slashwintostd(std::string s) { // convert "a\b\c" to "a/b/c" if windows
#ifdef _WIN32
    std::replace(s.begin(), s.end(), '\\', '/');
#endif
    return s;
}

inline std::string slashstdtowin(std::string s) { // convert "a/b/c" to "a\b\c" if windows
#ifdef _WIN32
    std::replace(s.begin(), s.end(), '/', '\\');
#endif
    return s;
}

inline std::filesystem::path gbkpath(std::string_view path) {
    return std::filesystem::path(path);
}

inline std::filesystem::path u8path(std::string_view path) {
#ifdef __cpp_char8_t // C++20 has deprecated u8path, we use path(u8string) overload instead
    auto pathu8p = reinterpret_cast<const char8_t *>(path.data());
    return std::filesystem::path(pathu8p, pathu8p + path.size());
#else
    return std::filesystem::u8path(path);
#endif
}

inline std::filesystem::path u8path(const char *path) {
#ifdef __cpp_char8_t // C++20 has deprecated u8path, we use path(u8string) overload instead
    auto pathu8p = reinterpret_cast<const char8_t *>(path);
    return std::filesystem::path(pathu8p);
#else
    return std::filesystem::u8path(path);
#endif
}

inline std::string pathu8(std::filesystem::path const &path) {
#ifdef __cpp_char8_t // since C++20, std::filesystem::u8string returns std::u8string instead of std::string
    auto p = path.generic_u8string();
    auto u8p = reinterpret_cast<const char *>(p.data());
    return std::string(u8p, u8p + p.size());
#else
    // path.generic_u8string() always returns "a/b/c" while path.u8string() may return "a\b\c" on win
    return path.generic_u8string();
#endif
}

inline std::string pathgbk(std::filesystem::path const &path) {
    return path.string();
}

inline std::string gbkpathtou8(std::string_view path) {
    return pathu8(gbkpath(path));
}

inline std::string u8pathtogbk(std::string_view path) {
    return pathgbk(u8path(path));
}

inline std::FILE *u8fopen(std::string_view path, const char *mode) {
    auto p = u8path(path);
#ifdef _WIN32
    wchar_t wmode[8];
    std::mbstowcs(wmode, mode, 8);
    return ::_wfopen(p.native().c_str(), wmode);
#else
    return std::fopen(p.string().c_str(), mode);
#endif
}

inline std::u32string u8tou32(std::string_view in) {
    std::mbstate_t mbs{};
#ifdef __cpp_char8_t
    const char8_t *from_next;
#else
    const char *from_next;
#endif
    char32_t *to_next;
    std::u32string out(in.size(), U'\0');
#ifdef __cpp_char8_t // C++20 deprecates std::codecvt<char32_t, char, std::mbstate_t>, use char8_t instead
    std::use_facet<std::codecvt<char32_t, char8_t, std::mbstate_t>>(std::locale()).in(
        mbs, (const char8_t *)in.data(), (const char8_t *)in.data() + in.size(), from_next,
        out.data(), out.data() + out.size(), to_next);
#else
    std::use_facet<std::codecvt<char32_t, char, std::mbstate_t>>(std::locale()).in(
        mbs, in.data(), in.data() + in.size(), from_next,
        out.data(), out.data() + out.size(), to_next);
#endif
    out.resize(to_next - out.data());
    return out;
}

inline std::string u32tou8(std::u32string_view in) {
    std::mbstate_t mbs{};
    const char32_t *from_next;
#ifdef __cpp_char8_t
    char8_t *to_next;
#else
    char *to_next;
#endif
    std::string out(in.size() * 4, '\0');
#ifdef __cpp_char8_t // C++20 deprecates std::codecvt<char32_t, char, std::mbstate_t>, use char8_t instead
    std::use_facet<std::codecvt<char32_t, char8_t, std::mbstate_t>>(std::locale()).out(
        mbs, in.data(), in.data() + in.size(), from_next,
        (char8_t *)out.data(), (char8_t *)out.data() + out.size(), to_next);
    out.resize((char *)to_next - out.data());
#else
    std::use_facet<std::codecvt<char32_t, char, std::mbstate_t>>(std::locale()).out(
        mbs, in.data(), in.data() + in.size(), from_next,
        out.data(), out.data() + out.size(), to_next);
    out.resize(to_next - out.data());
#endif
    return out;
}

}

// Usage example:
//
// std::u32string path = U"<path>";
// std::string utf8_path = u32tou8(path);
// auto native_path = std::filesystem::u8path(utf8_path);
// std::ifstream file(native_path);
// std::string utf8_line;
// file >> utf8_line;
// std::u32string line = u8tou32(utf8_line);
//
// So how to make portable UTF-8 application on windows? Conclusion:
//
// 1. we internally consider all std::string to be UTF-8, also turn your code editor into UTF-8 mode (for writting UTF-8 string literials)
// 2. specify the compiler option /utf-8 in MSVC, to make the compiler recoginize cpp source file as UTF-8 (done by Peng in CMakeLists.txt)
// 3. call SetConsoleOutputCP(65001) or system("chcp 65001") before main function, to prevent GBK console not recoginizing UTF-8 cout messages (done by Peng in src/zenoui/entrypoint.cpp)
// 4. all file path should be wrapped with std::filesystem::u8path or my wrapped zenox::u8path (this internally convert UTF-8 to GBK for you), for example, if you need to create an input stream std::ifstream file("文件路径"), instead you should use std::ifstream file(std::filesystem::u8path("文件路径")), otherwise you may result in "No such file or directory" due to std::ifstream assuming GBK encoding on windows, if you are calling an 3rdparty function that only accepts const char *, for example, stbi_load("文件路径"), then simply create the u8path class first and get back the raw GBK path using the string() method: stbi_load(std::filesystem::u8path("文件路径").string().c_str()), alternatively if you'd invoke an 'W' API for example CreateFileW(L"文件路径"), just use the wstring() method: CreateFileW(std::filesystem::u8path("文件路径").wstring().c_str())
// 5. all file content should be assumed to be UTF-8, for zsg files to be portable, as we already set UTF-8 code page for console and all std::string are considered to be UTF-8, so just read it via file >> s, and print it directly via cout << s
// 6. all program internally processed string should be UTF-32, which is std::u32string, to prevent character slicing errors (see below), unless your internal process doesn't require slicing a string (i.e. substr), then you're OK to use UTF-8 std::string
// 7. when you need a conversion between u32 and u8, use my zenox::u8tou32 and zenox::u32tou8 functions in this header
// 8. never use wstring, wifstream, wcout, wchar_t, these are neither portable or efficient (wchar_t is 16-bit on win, 32-bit on linux, they are not even having the same size)
// 9. never use u8string or u16string either, they are good features provided by standard comittee to ensure UTF-8 string literial support, however, as we've already set /utf-8 in MSVC, all string literials are already UTF-8, no need for u8 string literials any more, just take attention to use std::filesystem::u8path (UTF-8 path) instead of the default std::filesystem::path (GBK path)
//
// why prefer u32string in program internal?
// because for calculation of length, only the u32string is portably correct
// 1. string("我").size() may result in 3 on linux, 2 on win (after adding /utf-8 to MSVC, it would also be 3 on win)
// 2. wstring("我").size() may result in 1 on both linux and win, but it doesn't cover some corner cases like emojis
// 4. u32string(U"我").size() always result in 1, which is our expected result
//
// why calculating length is important? consider slicing a string:
// string("我是小彭老师").substr(0, 1)
// this will result in "�" which is LuanMa, since you sliced a 3-byte UTF-8 character apart.
// u32string("我是小彭老师").substr(0, 1)
// this in contrast would result in "我" correctly, since UTF-32 doesn't have any multi-byte character encodings.
//
// TL;DR: Use UTF-8 for the most part and convert to and from UTF-16 only when talking to the Windows interface.
